package physgin;

import javax.vecmath.Point3f;
import java.util.Comparator;

/**
 * Descibes the structure of a Body in a generic world.
 * Where a Body is any object within the world, other 
 * than the background.
 * @see     java.util.Comparator
 */
abstract class Body<B> implements Comparator<Body> 
    { 
        private Physics p;
        static final int sizePos = 3;
        
        /**
         * Returns the graphical representation of this Body object, in
         * a graphics library dependant way, as specified when creating
         * the Body object.
         * 
         * @return      An object of type B representing this object in a graphical way.
         */
        abstract B getObject();
        
        /**
         * Updates this Body objects position in the graphical representation.
         */
        abstract void update();
        
        /**
         * Default constructor.
         * Builds a Body of the shape specified, with the parameters specified.
         * Takes a variable length of floats which specify parameters the 
         * particular order.
         * position(x&y), mass, size, velocity, orientation, elasticity
         * 0,1            2,    3,    4,5       6,           7
         *
         * @param   ss  Shapes value to specify the type of Shape of the Body
         * @param   fs  variable length list of floats for Body paramters
         * @see         Shapes
         */
        Body(Shapes ss, float... fs)
        {
            Point3f p = new Point3f(0f,0f,-4.0f);
            Point3f v = new Point3f(0f,0f,0f);
            float m = 1f;
            float s = 1f;
            float o = 0f;
            float e = 0.8f;
            
            if (fs.length >= 2)
                p = new Point3f(fs[0], fs[1], -4.0f);
            if (fs.length >= 3)
                m = fs[2];
            if (fs.length >= 4)
                s = fs[3];
            if (fs.length >= 6)
                v = new Point3f(fs[4], fs[5], 0f);
            if (fs.length >= 7)
                o = fs[6];
            if (fs.length == 8)
                e = fs[7];
            
            
            switch (ss)
            {
                case CIRCLE:
                    this.p = new CirclePhysics(e, m, s, o, p, v);
                    break;
                case SQUARE:
                    this.p = new SquarePhysics(e, m, s, o, p, v);
                    break;
            }
        }
        
        /**
         * Returns the position of the Body object in space.
         *
         * @return      Point3f of the Bodys position
         * @see         javax.vecmath.Point3f
         */
        Point3f position()
        {
            return getPhys().getPos();
        }
        
        /**
         * Returns the orientation of the Body object in radians.
         *
         * @return      float of the Bodys position in radians.
         */
        float orientation()
        {
            return p.getOrient();
        }
        
        /**
         * Returns a copy of the Physics object associated with this Body.
         *
         * @return      Physics object describing the Bodys physics state.
         * @see         Physics
         */
        synchronized Physics getPhys()
        {
            return p.getPhysics();
        }
        
        /**
         * Resoles a collision of this Body with the physics state
         * (of another Body) specified.
         *
         * @param   pB  Physics state of the coliding Body
         * @see         Physics
         */
        synchronized void resolve(Physics pB)
        {
            p.resolve(pB);
        }
        
        /**
         * Updates the Body objects position in space.
         */
        synchronized void updatePos()
        {
            p.updatePos();
        }
        
        /**
         * Compare the two Body objects specified, in terms of location in space.
         * If they are colliding returns 0, anything else indicates they are not.
         *
         * @param   a   Body object for comparison.
         * @param   b   Body object for comparison
         * @return      int specifying whether the two objects are comparable in space, 0 being they are.
         */
        public int compare(Body a, Body b)
        {
            if (a.equals(b))
                return 0;
            else
                return 1;
        }
  
        /**
         * Returns the shape of this Body object.
         *
         * @return      Shapes value for the shape of this object.
         */
        Shapes getShape()
        {
          return p.getShape();
        }
  
        /**
         * Compares this Body to another Body object in space. Returns a boolean specifying 
         * whether or not the two Body objects are colliding.
         *
         * @param   b   Body object to compare to.
         * @return      boolean indicating whether they are colliding.
         */
        public boolean equals(Body b)
        {
          Physics pB = b.getPhys();
          Point3f ap = p.getPos();
          Point3f bp = pB.getPos();
          double diff;
          
          // CASE:: If one or more is square
          if((b.getShape() == Shapes.SQUARE && getShape() == Shapes.CIRCLE) || (getShape() == Shapes.CIRCLE && b.getShape() == Shapes.SQUARE))
          {
            if(Math.abs(ap.x - bp.x) < Math.abs(ap.y - bp.y)) //Circle is above/below square
            {
              diff = Math.abs(ap.y - bp.y);
            float diffb = Math.abs(ap.x - bp.x)+0.001f;
            
            if(diff < (p.getSize() + pB.getSize())&& diffb < (p.getSize() + pB.getSize()))
              return true;
            else
              return false;
          }
          else if(Math.abs(ap.x - bp.x) == Math.abs(ap.y - bp.y)) // Corner Collision
          {
            diff = Math.sqrt(Math.pow((ap.x - bp.x),2.0d) + Math.pow((ap.y - bp.y),2.0d));
            if(getShape() == Shapes.CIRCLE) //this one is the Circle, other is square
            {
              if(diff < (p.getSize() + Math.sqrt(2*(Math.pow(pB.getSize(),2.0d)))))
                return true;
              else
                return false;
            }
            else if(b.getShape() == Shapes.SQUARE) // this one is square, other = circle
            {
              if(diff < (pB.getSize() + Math.sqrt(2*(Math.pow(p.getSize(),2.0d)))))
                return true;
              else
                return false;
            }
            else // Both squares
            {
              if(diff < ( Math.sqrt(2*(Math.pow(p.getSize(),2.0d))) + Math.sqrt(2*(Math.pow(pB.getSize(),2.0d)))))
                return true;
              else
                return false;
            }
          }
          else //Circle is left/right of square
          {
            diff = Math.abs(ap.x - bp.x)+0.01;
            float diffb = Math.abs(ap.y - bp.y)+0.01f;
            if(diff < (p.getSize() + pB.getSize()) && diffb < (p.getSize() + pB.getSize()) )
              return true;
            else
              return false;
          }
        }

          // CASE: Both Circles
          diff = Math.sqrt(Math.pow((ap.x - bp.x),2.0d) + Math.pow((ap.y - bp.y),2.0d));
      
          if(diff <= (p.getSize() + pB.getSize()))
            return true;
          else
            return false;
        }
}
